# 字符串操作 - strings包

# strings - 字符串操作

# 字符串比较

  1. Compare 函数:用于比较两个字符串的大小,如果两个字符串相等,返回为 0。如果 a 小于 b ,返回 -1 ,反之返回 1 。不推荐使用这个函数,直接使用 == != > < >= <= 等一系列运算符更加直观。

    func Compare(a, b string) int 
    
  2. EqualFold 函数,计算 s 与 t 忽略字母大小写后是否相等。

    func EqualFold(s, t string) bool
    

实例

package main

import (
	"fmt"
	"strings"
)

func main() {
	s1 := "您好"
	s2 := "你好"
	fmt.Println("--> Compare")
	fmt.Println("s1/s2:", strings.Compare(s1, s2))
	fmt.Println("s1/s2:", strings.Compare(s2, s1))
	fmt.Println("s1/s2:", strings.Compare(s2, s2))

	fmt.Println("--> EqualFold")
	fmt.Println("s1/s2:", strings.EqualFold(s1, s2))
	fmt.Println("s1/s2:", strings.EqualFold(s2, s1))
	fmt.Println("s1/s2:", strings.EqualFold(s2, s2))
}

// 结果:
--> Compare
s1/s2: 1
s1/s2: -1
s1/s2: 0
--> EqualFold
s1/s2: false
s1/s2: false
s1/s2: true

# 是否存在某个字符或子串

  1. 子串 substr 在 s 中,返回 true,如果多字符匹配,那么会拿整个字符串去匹配,成功返回true

    func Contains(s, substr string) bool
    
  2. chars 中任何一个 Unicode 代码点在 s 中,返回 true,如果多字符匹配,那么任意一个字符匹配成功返回true

    func ContainsAny(s, chars string) bool
    
  3. Unicode 代码点 r 在 s 中,返回 true,单字符匹配成功返回true

    func ContainsRune(s string, r rune) bool
    

实例

package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Println("--> Contains") // 如果是多字符,那么就拿整个字符串去匹配
	fmt.Println(strings.Contains("hello", "e"))
	fmt.Println(strings.Contains("hello", "s"))

	fmt.Println("--> ContainsAny") // 如果是多字符,那么就是一个一个字符去匹配,只要有一个匹配成功,就为true
	fmt.Println(strings.ContainsAny("hell o", "e"))
	fmt.Println(strings.ContainsAny("hell o", "s"))
	fmt.Println(strings.ContainsAny("hell o", "s & e"))
	fmt.Println(strings.ContainsAny("hell o", "ee"))
	fmt.Println(strings.ContainsAny("hell o", " "))
	fmt.Println(strings.ContainsAny("hell o", ""))

	fmt.Println("--> ContainsRune") // 只能使用单字符,多字符会报错
	fmt.Println(strings.ContainsRune("hell o", 'e'))
	fmt.Println(strings.ContainsRune("hell o", ' '))
	fmt.Println(strings.ContainsRune("hell o", 's'))
}

// 结果:
--> Contains
true
false
--> ContainsAny
true
false
true
true
true
false
--> ContainsRune
true
true
false

# 子串出现次数 ( 字符串匹配 )

查找子串出现次数即字符串模式匹配,实现的是 Rabin-Karp 算法

func Count(s, sep string) int

实例

package main

import (
	"fmt"
	"strings"
)

func main() {
	s1 := "您好中国"
	fmt.Println(len(s1))
	fmt.Println(strings.Count(s1, "您好"))
	fmt.Println(strings.Count(s1, "你好"))

	s2 := "hellose"
	fmt.Println(len(s2))
	fmt.Println(strings.Count(s2, "l"))
    fmt.Println(strings.Count(s2, "")) // 匹配的值为空,那么就是len(s2) + 1

}

// 结果:
12
1
0
7
2
8

# 字符串分割为[]string

该包提供了六个三组分割函数:Fields 和 FieldsFunc、Split 和 SplitAfter、SplitN 和 SplitAfterN。

# Fields - 间隔符切割非自定义

Fields 用一个或多个连续的空格分隔字符串 s,返回子字符串的数组(slice)。如果字符串 s 只包含空格,则返回空列表 ([]string 的长度为 0)。其中,空格的定义是 unicode.IsSpace,之前已经介绍过。

常见间隔符包括:’\t’, ‘\n’, ‘\v’, ‘\f’, ‘\r’, ‘ ‘, U+0085 (NEL), U+00A0 (NBSP)

func Fields(s string) []string

实例

package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Println(strings.Fields("s e dddd ss 9  1\n1"))
}

//结果:
[s e dddd ss 9 1 1]

# FieldsFunc自定义函数切割

FieldsFunc 用这样的 Unicode 代码点 c 进行分隔:满足 f(c) 返回 true。该函数返回[]string。如果字符串 s 中所有的代码点 (unicode code points) 都满足 f(c) 或者 s 是空,则 FieldsFunc 返回空 slice。

也就是说,我们可以通过实现一个回调函数来指定分隔字符串 s 的字符

func FieldsFunc(s string, f func(rune) bool) []string

实例

通过函数返回true或false来判断是否切割,true不切割,false切割,这样就可以定制自定义的切割函数

实际上,Fields 函数就是调用 FieldsFunc 实现的

一般跟unicode包一起使用

package main

import (
	"fmt"
	"strings"
	"unicode"
)

func main() {
	f := func(c rune) bool {
		return unicode.IsNumber(c)
	}
	fmt.Println(strings.FieldsFunc("s1e1s1e1", f))

	f1 := func(c rune) bool {
		return c == 115
	}
	fmt.Println(strings.FieldsFunc("s1e1s1e1", f1))
}

// 结果:
[s e s e]
[1e1 1e1]

# Split 、SplitAfter、SplitN、SplitAfterN - 自定义切割符

四个函数,因为它们都是通过一个同一个内部函数来实现的,实现越来相差不大

func Split(s, sep string) []string { return genSplit(s, sep, 0, -1) }
func SplitAfter(s, sep string) []string { return genSplit(s, sep, len(sep), -1) }
func SplitN(s, sep string, n int) []string { return genSplit(s, sep, 0, n) }
func SplitAfterN(s, sep string, n int) []string { return genSplit(s, sep, len(sep), n) }

实例

package main

import (
	"fmt"
	"strings"
)

func main() {
	s := "sd,321,1ss,s,fas1,sad1,s,1,"

	fmt.Println("--> Split") // 切割时会把切割符给去掉
	fmt.Println(strings.Split(s, ","))

	fmt.Println("--> SplitAfter") // 切割时保留切割符
	fmt.Println(strings.SplitAfter(s, ","))

	fmt.Println("--> SplitN") // 切割时,N的位置是指定[]string长度,也代表最多可以切割成几个元素,切割时会把切割符给去掉
	fmt.Println(strings.SplitN(s, ",", 1))
	fmt.Println(strings.SplitN(s, ",", 3))

	fmt.Println("--> SplitAfterN") // 切割时,N的位置是指定[]string长度,也代表最多可以切割成几个元素,切割时保留切割符
	fmt.Println(strings.SplitAfterN(s, ",", 1))
	fmt.Println(strings.SplitAfterN(s, ",", 3))
}

//结果
--> Split
[sd 321 1ss s fas1 sad1 s 1 ]
--> SplitAfter
[sd, 321, 1ss, s, fas1, sad1, s, 1, ]
--> SplitN
[sd,321,1ss,s,fas1,sad1,s,1,]
[sd 321 1ss,s,fas1,sad1,s,1,]
--> SplitAfterN
[sd,321,1ss,s,fas1,sad1,s,1,]
[sd, 321, 1ss,s,fas1,sad1,s,1,]

# 字符串是否有某个前缀或后缀

# 指定字符是否前缀开头 - HasPrefix

func HasPrefix(s, prefix string) bool {
  return len(s) >= len(prefix) && s[0:len(prefix)] == prefix
}

实例

package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Println(strings.HasPrefix("hello GO", "he"))
	fmt.Println(strings.HasPrefix("hello GO", "GO"))
	fmt.Println(strings.HasPrefix("hello GO", "")) // 如果匹配值为空,那么返回true
}

//结果:
true
false
true

# 指定字符是否后缀结尾 - HasSuffix

func HasSuffix(s, suffix string) bool {
  return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix
}

实例

package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Println(strings.HasSuffix("hello GO", "he"))
	fmt.Println(strings.HasSuffix("hello GO", "GO"))
	fmt.Println(strings.HasSuffix("hello GO", "")) // 如果匹配值为空,那么返回true
}

// 结果:
false
true
true

# 字符或子串在字符串中出现的位置

# 首次出现

// 在 s 中查找 sep 的第一次出现,返回第一次出现的索引
func Index(s, sep string) int

// 在 s 中查找字节 c 的第一次出现,返回第一次出现的索引
func IndexByte(s string, c byte) int

// chars 中任何一个 Unicode 代码点在 s 中首次出现的位置
func IndexAny(s, chars string) int

// 查找字符 c 在 s 中第一次出现的位置,其中 c 满足 f(c) 返回 true
func IndexFunc(s string, f func(rune) bool) int

// Unicode 代码点 r 在 s 中第一次出现的位置
func IndexRune(s string, r rune) int

实例

package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Println("--> Index") // 找到返回索引,找不到返回-1
	fmt.Println(strings.Index("hollo", "o"))
	fmt.Println(strings.Index("hollo", ""))

	fmt.Println("--> IndexByte") // 找到返回索引,找不到返回-1,为空会报错
	fmt.Println(strings.IndexByte("hollo", 'o'))
	fmt.Println(strings.IndexByte("hollo", 's'))
	//fmt.Println(strings.IndexByte("hollo", '')) 为空报错

	fmt.Println("--> IndexAny") // 找到返回索引,找不到返回-1
	s1 := strings.IndexAny("hoolo", "o")
	s2 := strings.IndexAny("hoolo", "")
	fmt.Println(s1)
	fmt.Println(s2)

	fmt.Println("--> IndexFunc") // 使用自定义函数自行判断,找到返回索引,找不到返回-1
	c1 := func(c rune) bool {
		return string(c) == "o"
	}
	fmt.Println(strings.IndexFunc("hollo", c1))

	fmt.Println("--> IndexRune") // 找到返回索引,找不到返回-1
	fmt.Println(strings.IndexRune("hollo", 'o'))
	fmt.Println(strings.IndexRune("hollo", 's'))
	//fmt.Println(strings.IndexRune("hollo", ''))

}

// 结果:
--> Index
1
0
--> IndexByte
1
-1
--> IndexAny
1
-1
--> IndexFunc
1
--> IndexRune
1
-1

# 最后出现

// 在 s 中查找 sep 的最后出现,返回最后出现的索引
func LastIndex(s, sep string) int

// 在 s 中查找字节 c 的最后出现,返回最后出现的索引
func LastIndexByte(s string, c byte) int

// chars 中任何一个 Unicode 代码点在 s 中最后出现的位置
func LastIndexAny(s, chars string) int

// 查找字符 c 在 s 中最后出现的位置,其中 c 满足 f(c) 返回 true
func LastIndexFunc(s string, f func(rune) bool) int

实例

package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Println("--> LastIndex") // 找到返回索引,找不到返回-1
	fmt.Println(strings.LastIndex("hollo", "o"))
	fmt.Println(strings.LastIndex("hollo", "s"))

	fmt.Println("--> LastIndexByte") // 找到返回索引,找不到返回-1,为空会报错
	fmt.Println(strings.LastIndexByte("hollo", 'o'))
	fmt.Println(strings.LastIndexByte("hollo", 's'))
	//fmt.Println(strings.IndexByte("hollo", '')) 为空报错

	fmt.Println("--> LastIndexAny") // 找到返回索引,找不到返回-1
	s1 := strings.LastIndexAny("hoolo", "o")
	s2 := strings.LastIndexAny("hoolo", "")
	fmt.Println(s1)
	fmt.Println(s2)

	fmt.Println("--> LastIndexFunc") // 使用自定义函数自行判断,找到返回索引,找不到返回-1
	c1 := func(c rune) bool {
		return string(c) == "o"
	}
	fmt.Println(strings.LastIndexFunc("hollo", c1))
}

//结果:
--> LastIndex
4
-1
--> LastIndexByte
4
-1
--> LastIndexAny
4
-1
--> LastIndexFunc
4

# 字符串 JOIN 操作 - 拼接

将字符串数组(或 slice)连接起来可以通过 Join 实现

func Join(a []string, sep string) string

实例

package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Println(strings.Join([]string{"name=msg", "age=22"}, "&"))
}

// 结果:
name=msg&age=22

# 想让字符串重复次数

想让字符串重复多少,就不用复制了

func Repeat(s string, count int) string

实例

package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Println(strings.Repeat("hello\n", 2))
}

//结果:
hello
hello

# 字符替换

Map 函数,将 s 的每一个字符按照 mapping 的规则做映射替换,如果 mapping 返回值 <0 ,则舍弃该字符。该方法只能对每一个字符做处理,但处理方式很灵活,可以方便的过滤,筛选汉字等

func Map(mapping func(rune) rune, s string) string

实例

package main

import (
	"fmt"
	"strings"
	"unicode"
)

func main() {
	mapping := func(r rune) rune {
		switch {
		case r >= 'A' && r <= 'Z': // 大写字母转小写
			return r + 32
		case r >= 'a' && r <= 'z': // 小写字母不处理
			return r
		case unicode.Is(unicode.Han, r): // 汉字换行
			return '\n'
		}
		return -1 // 过滤所有非字母、汉字的字符
	}
	fmt.Println(strings.Map(mapping, "Hello你#¥%……\n('World\n,好Hello^(&(*界gopher..."))
}

// 结果:
hello
world
hello
gopher

# 字符串子串替换

进行字符串替换时,考虑到性能问题,能不用正则尽量别用

// 用 new 替换 s 中的 old,一共替换 n 个。
// 如果 n < 0,则不限制替换次数,即全部替换
func Replace(s, old, new string, n int) string

// 该函数内部直接调用了函数 Replace(s, old, new , -1)
func ReplaceAll(s, old, new string) string

实例

package main

import (
	"fmt"
	"strings"
)

func main() {
	fmt.Println("--> Replace")
	fmt.Println(strings.Replace("sseesfsaff", "s", "e", 1)) // s:要替换的字符串,old:替换字符串,new:原字符串,n:要替换几个
	fmt.Println(strings.Replace("sseesfsaff", "s", "e", -1)) // s:要替换的字符串,old:替换字符串,new:原字符串,n:要替换几个,-1:不限

	fmt.Println("--> ReplaceAll")
	fmt.Println(strings.ReplaceAll("sseesfsaff", "s", "e")) // s:要替换的字符串,old:替换字符串,new:原字符串,符合的全部替换
}


// 结果:
--> Replace
eseesfsaff
eeeeefeaff
--> ReplaceAll
eeeeefeaff

# 大小写转换

大小写转换包含了 4 个相关函数,ToLower,ToUpper 用于大小写转换。ToLowerSpecial,ToUpperSpecial 可以转换特殊字符的大小写。

func ToLower(s string) string
func ToLowerSpecial(c unicode.SpecialCase, s string) string
func ToUpper(s string) string
func ToUpperSpecial(c unicode.SpecialCase, s string) string

实例

package main

import (
	"fmt"
	"strings"
	"unicode"
)

func main() {
	fmt.Println(strings.ToLower("HELLO WORLD"))
	fmt.Println(strings.ToLower("Ā Á Ǎ À"))
	fmt.Println(strings.ToLowerSpecial(unicode.TurkishCase, "壹"))
	fmt.Println(strings.ToLowerSpecial(unicode.TurkishCase, "HELLO WORLD"))
	fmt.Println(strings.ToLower("Önnek İş"))
	fmt.Println(strings.ToLowerSpecial(unicode.TurkishCase, "Önnek İş"))

	fmt.Println(strings.ToUpper("hello world"))
	fmt.Println(strings.ToUpper("ā á ǎ à"))
	fmt.Println(strings.ToUpperSpecial(unicode.TurkishCase, "一"))
	fmt.Println(strings.ToUpperSpecial(unicode.TurkishCase, "hello world"))
	fmt.Println(strings.ToUpper("örnek iş"))
	fmt.Println(strings.ToUpperSpecial(unicode.TurkishCase, "örnek iş"))
}

// 结果:
hello world
ā á ǎ à
壹
hello world
önnek iş
önnek iş
HELLO WORLD
Ā Á Ǎ À
一
HELLO WORLD
ÖRNEK IŞ
ÖRNEK İŞ

# 字母处理

// 每个单词的首字母大写,不处理该单词的后续字符
func Title(s string) string

// 每个字母大写
func ToTitle(s string) string

// 每个字母大写,可以处理一些特殊字符字母
func ToTitleSpecial(c unicode.SpecialCase, s string) string

实例

package main

import (
	"fmt"
	"strings"
	"unicode"
)

func main() {
	fmt.Println(strings.Title("hElLo wOrLd"))
	fmt.Println(strings.ToTitle("hElLo wOrLd"))
	fmt.Println(strings.ToTitleSpecial(unicode.TurkishCase, "hElLo wOrLd"))

	fmt.Println(strings.Title("āáǎà ōóǒò êēéěè"))
	fmt.Println(strings.ToTitle("āáǎà ōóǒò êēéěè"))
	fmt.Println(strings.ToTitleSpecial(unicode.TurkishCase, "āáǎà ōóǒò êēéěè"))

	fmt.Println(strings.Title("dünyanın ilk borsa yapısı Aizonai kabul edilir"))
	fmt.Println(strings.ToTitle("dünyanın ilk borsa yapısı Aizonai kabul edilir"))
	fmt.Println(strings.ToTitleSpecial(unicode.TurkishCase, "dünyanın ilk borsa yapısı Aizonai kabul edilir"))
}

// 结果:
HElLo WOrLd
HELLO WORLD
HELLO WORLD
Āáǎà Ōóǒò Êēéěè
ĀÁǍÀ ŌÓǑÒ ÊĒÉĚÈ
ĀÁǍÀ ŌÓǑÒ ÊĒÉĚÈ
Dünyanın Ilk Borsa Yapısı Aizonai Kabul Edilir
DÜNYANIN ILK BORSA YAPISI AIZONAI KABUL EDILIR
DÜNYANIN İLK BORSA YAPISI AİZONAİ KABUL EDİLİR

# 去除指定的字符

// 将 s 左侧和右侧中匹配 cutset 中的任一字符的字符去掉
func Trim(s string, cutset string) string

// 将 s 左侧的匹配 cutset 中的任一字符的字符去掉
func TrimLeft(s string, cutset string) string

// 将 s 右侧的匹配 cutset 中的任一字符的字符去掉
func TrimRight(s string, cutset string) string

// 如果 s 的前缀为 prefix 则返回去掉前缀后的 string , 否则 s 没有变化。
func TrimPrefix(s, prefix string) string

// 如果 s 的后缀为 suffix 则返回去掉后缀后的 string , 否则 s 没有变化。
func TrimSuffix(s, suffix string) string

// 将 s 左侧和右侧的间隔符去掉。常见间隔符包括:'\t', '\n', '\v', '\f', '\r', ' ', U+0085 (NEL)
func TrimSpace(s string) string

// 将 s 左侧和右侧的匹配 f 的字符去掉
func TrimFunc(s string, f func(rune) bool) string

// 将 s 左侧的匹配 f 的字符去掉
func TrimLeftFunc(s string, f func(rune) bool) string

// 将 s 右侧的匹配 f 的字符去掉
func TrimRightFunc(s string, f func(rune) bool) string

实例

package main

import (
	"fmt"
	"strings"
	"unicode"
)

func main() {
	x := "!!!@@@你好,!@#$ Gophers###$$$"

	fmt.Println(strings.Trim(x, "@#$!%^&*()_+=-"))

	fmt.Println(strings.TrimLeft(x, "@#$!%^&*()_+=-"))

	fmt.Println(strings.TrimRight(x, "@#$!%^&*()_+=-"))

	fmt.Println(strings.TrimSpace(" \t\n Hello, Gophers \n\t\r\n"))

	fmt.Println(strings.TrimPrefix(x, "!"))

	fmt.Println(strings.TrimSuffix(x, "$"))

	
	f := func(r rune) bool {
		return !unicode.Is(unicode.Han, r) // 非汉字返回 true
	}
	fmt.Println(strings.TrimFunc(x, f))

	fmt.Println(strings.TrimLeftFunc(x, f))

	fmt.Println(strings.TrimRightFunc(x, f))
}

// 结果:
你好,!@#$ Gophers
你好,!@#$ Gophers###$$$
!!!@@@你好,!@#$ Gophers
Hello, Gophers
!!@@@你好,!@#$ Gophers###$$$
!!!@@@你好,!@#$ Gophers###$$
你好
你好,!@#$ Gophers###$$$
!!!@@@你好

# 多值替换

这是一个结构,没有导出任何字段,实例化通过 func NewReplacer(oldnew ...string) *Replacer 函数进行,其中不定参数 oldnew 是 old-new 对,即进行多个替换。如果 oldnew 长度与奇数,会导致 panic.

package main

import (
	"fmt"
	"strings"
)

func main() {
	r := strings.NewReplacer("<", "&lt;", ">", "&gt;")
	fmt.Println(r.Replace("This is <b>HTML</b>!"))
}

// 结果:
This is &lt;b&gt;HTML&lt;/b&gt;!

# Reader 类型

看到名字就能猜到,这是实现了 io 包中的接口。它实现了 io.Reader(Read 方法),io.ReaderAt(ReadAt 方法),io.Seeker(Seek 方法),io.WriterTo(WriteTo 方法),io.ByteReader(ReadByte 方法),io.ByteScanner(ReadByte 和 UnreadByte 方法),io.RuneReader(ReadRune 方法) 和 io.RuneScanner(ReadRune 和 UnreadRune 方法)。

Reader 结构如下:

type Reader struct {
  s        string    // Reader 读取的数据来源
  i        int // current reading index(当前读的索引位置)
  prevRune int // index of previous rune; or < 0(前一个读取的 rune 索引位置)
}

可见 Reader 结构没有导出任何字段,而是提供一个实例化方法:

func NewReader(s string) *Reader

该方法接收一个字符串,返回的 Reader 实例就是从该参数字符串读数据。在后面学习了 bytes 包之后,可以知道 bytes.NewBufferString 有类似的功能,不过,如果只是为了读取,NewReader 会更高效。

其他方法不介绍了,都是之前接口的实现,有兴趣的可以看看源码实现,大部分都是根据 i、prevRune 两个属性来控制。

# Builder 类型

type Builder struct {
    addr *Builder // of receiver, to detect copies by value
    buf  []byte
}

该类型实现了 io 包下的 Writer, ByteWriter, StringWriter 等接口,可以向该对象内写入数据,Builder 没有实现 Reader 等接口,所以该类型不可读,但提供了 String 方法可以获取对象内的数据。

// 该方法向 b 写入一个字节
func (b *Builder) WriteByte(c byte) error
// WriteRune 方法向 b 写入一个字符
func (b *Builder) WriteRune(r rune) (int, error)
// WriteRune 方法向 b 写入字节数组 p
func (b *Builder) Write(p []byte) (int, error)
// WriteRune 方法向 b 写入字符串 s
func (b *Builder) WriteString(s string) (int, error)
// Len 方法返回 b 的数据长度。
func (b *Builder) Len() int
// Cap 方法返回 b 的 cap。
func (b *Builder) Cap() int
// Grow 方法将 b 的 cap 至少增加 n (可能会更多)。如果 n 为负数,会导致 panic。
func (b *Builder) Grow(n int)
// Reset 方法将 b 清空 b 的所有内容。
func (b *Builder) Reset()
// String 方法将 b 的数据以 string 类型返回。
func (b *Builder) String() string

Builder 有 4 个与写入相关的方法,这 4 个方法的 error 都总是为 nil.

Builder 的 cap 会自动增长,一般不需要手动调用 Grow 方法。

String 方法可以方便的获取 Builder 的内容。

举个例子:

b := strings.Builder{}
_ = b.WriteByte('7')
n, _ := b.WriteRune('夕')
fmt.Println(n)
n, _ = b.Write([]byte("Hello, World"))
fmt.Println(n)
n, _ = b.WriteString("你好,世界")
fmt.Println(n)
fmt.Println(b.Len())
fmt.Println(b.Cap())
b.Grow(100)
fmt.Println(b.Len())
fmt.Println(b.Cap())
fmt.Println(b.String())
b.Reset()
fmt.Println(b.String())

输出结果:

3
12
15
31
32
31
164
7夕Hello, World你好,世界